'''
Program Assignment:	Assignment 2
Name:			Alberto Massa                                                                      
Date:			11/08/2010                  
Description:            Python language parse file and some good stuffs. 
'''

'''
Listing Contents:
Compilation instructions
	This module does not contains runnable standalone code.

Includes and dependecies
	lang.py module must be in PYTHONPATH or in same directory
	
	used buit-in modules: re, itertools, pprint

Class and procedures declarations:                                                                                     	
	useful utilities and consts for parse python files (like remove_noncode)
	make_tree: core function for abstract syntax tree
	PythonParser: class for handle all those
	
'''

import re
import itertools as it
from pprint import pprint

from lang import Token 

# python character for singleline comment
SINGLELINE_COMMENT = '#'

# python regexp pattern for multiline comment
# note that open comment and close comment has the same pattern
MULTILINE_COMMENT1 = '["]{3}'
MULTILINE_COMMENT2 = "[']{3}"

# python token that will have to count
toks = set(['def ', 'class '])


def line_has_token(line):
	'''
	Return a token string if line contains it, blak string otherwise
	'''
	for t in toks:
		if t in line: 
			return t
	return ""

def detokenize_line(line, token):
	'''
	Given a line and a token string, return name after token without a single space. Parenthesis must be in line.
	For example:
		line: "def foo(): pass"
		token: "def"
		returned: "foo"

	But:
		line: "class MyObj: pass"
		token: "class"
		returned: False

		also if a python correct syntax whitout parenthesis it will not recognize.
	'''
	# take only word after token
	_name = re.findall(r'[('+token+' )].*[(]', line)[0]
	_name = _name.split()[1][:-1]
	return _name

def remove_noncode(string):
	'''
	Remove python idioms for single line comments, multiline strings and blanks.
	Returned the decommentated string.
	'''
	# remove single line comments
	lines = []
	for line in string.split('\n'):
		parts = line.split(SINGLELINE_COMMENT)
		lines.append(parts[0])
	# remake string
	string = '\n'.join(lines)

	# remove multiline strings
	def remove_multiline(k, string):
		r = '.*?'.join([k]*2)
		multiline_pattern = re.compile(r, re.DOTALL)
		#return re.sub(multiline_pattern, '"here was a multiline string"', string)
		return re.sub(multiline_pattern, '', string)

	string = remove_multiline(MULTILINE_COMMENT1, string)
	string = remove_multiline(MULTILINE_COMMENT2, string)

	lines = string.split('\n')
	string = '\n'.join(l for l in lines if l.strip())

	return string


def count_tab(string):
	'''
	Count tab before any other character.
	Example:
		string: '\t\t\tpass\t  \t'
		returned value: 3
	'''
	n = 0
	tabs = list(it.takewhile(lambda x: x=='\t', string))
	return len(tabs)


def make_tree(lines, name, parent, depth):
	'''
	Main function for construct a simple syntax abstract tree from a python source string.
	It takes:
		lines: list of lines containing the code (usually obtained with string.split() method)
		name: string with name of token of the block of lines (first time called it shoud be 'root' or 'body')
		parent: Token object that represent the parent of block (first time called it shoud be None)
		depth: depth of block (first time called it shoud be 0)

	Returned value:
		a Token object that represent the root of block of given lines,
		integer with loc.
	'''
	node = Token(name, parent, depth)
	i = 0
	while i<len(lines):
		line = lines[i]
		token = line_has_token(line)
		t = count_tab(line)

		if t >= depth:
			if token:
				# tree recursion
				_name = detokenize_line(line, token)
				n,j = make_tree(lines[i+1:], _name, node, depth+1)
				node.addchild(n)
				i += j
			else:
				# if, for, etc
				pass
		elif t < depth:
			# end of block
			break
		i += 1
	node.line = i
	return node, i

class PythonParser():
	'''
	Class that handle all process of parsing python source file.
	'''

	def parse_file(self, codefile):
		'''
		Return a Token object representing the root node of a simple syntax abstract tree of the given CodeFile object.
		Only 'def' and 'class' token are matched.
		See Coding Standars paper of this PSP assigment for more information.
		'''
		body = open(codefile.path).read()
		body = remove_noncode(body)

		root, dropped = make_tree(body.split('\n'), "root", None, 0)
		codefile.root = root
		
		return root
